SPORE.$Import('core.slick.parser');

/*
---
name: Slick.Finder
description: The new, superfast css selector engine.
provides: Slick.Finder
requires: Slick.Parser
...
*/

SPORE.register('core.slick.finder',function($ns){

	;(function(){
	
	var local = {},
		featuresCache = {},
		toString = Object.prototype.toString;
	
	// Feature / Bug detection
	
	local.isNativeCode = function(fn){
		return (/\{\s*\[native code\]\s*\}/).test('' + fn);
	};
	
	local.isXML = function(document){
		return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
		(document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
	};
	
	local.setDocument = function(document){
	
		// convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
		var nodeType = document.nodeType;
		if (nodeType == 9); // document
		else if (nodeType) document = document.ownerDocument; // node
		else if (document.navigator) document = document.document; // window
		else return;
	
		// check if it's the old document
	
		if (this.document === document) return;
		this.document = document;
	
		// check if we have done feature detection on this document before
	
		var root = document.documentElement,
			rootUid = this.getUIDXML(root),
			features = featuresCache[rootUid],
			feature;
	
		if (features){
			for (feature in features){
				this[feature] = features[feature];
			}
			return;
		}
	
		features = featuresCache[rootUid] = {};
	
		features.root = root;
		features.isXMLDocument = this.isXML(document);
	
		features.brokenStarGEBTN
		= features.starSelectsClosedQSA
		= features.idGetsName
		= features.brokenMixedCaseQSA
		= features.brokenGEBCN
		= features.brokenCheckedQSA
		= features.brokenEmptyAttributeQSA
		= features.isHTMLDocument
		= features.nativeMatchesSelector
		= false;
	
		var starSelectsClosed, starSelectsComments,
			brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
			brokenFormAttributeGetter;
	
		var selected, id = 'slick_uniqueid';
		var testNode = document.createElement('div');
		
		var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
		testRoot.appendChild(testNode);
	
		// on non-HTML documents innerHTML and getElementsById doesnt work properly
		try {
			testNode.innerHTML = '<a id="'+id+'"></a>';
			features.isHTMLDocument = !!document.getElementById(id);
		} catch(e){};
	
		if (features.isHTMLDocument){
	
			testNode.style.display = 'none';
	
			// IE returns comment nodes for getElementsByTagName('*') for some documents
			testNode.appendChild(document.createComment(''));
			starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
	
			// IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
			try {
				testNode.innerHTML = 'foo</foo>';
				selected = testNode.getElementsByTagName('*');
				starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
			} catch(e){};
	
			features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
	
			// IE returns elements with the name instead of just id for getElementsById for some documents
			try {
				testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
				features.idGetsName = document.getElementById(id) === testNode.firstChild;
			} catch(e){};
	
			if (testNode.getElementsByClassName){
	
				// Safari 3.2 getElementsByClassName caches results
				try {
					testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
					testNode.getElementsByClassName('b').length;
					testNode.firstChild.className = 'b';
					cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
				} catch(e){};
	
				// Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
				try {
					testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
					brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
				} catch(e){};
	
				features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
			}
			
			if (testNode.querySelectorAll){
				// IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
				try {
					testNode.innerHTML = 'foo</foo>';
					selected = testNode.querySelectorAll('*');
					features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
				} catch(e){};
	
				// Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
				try {
					testNode.innerHTML = '<a class="MiX"></a>';
					features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
				} catch(e){};
	
				// Webkit and Opera dont return selected options on querySelectorAll
				try {
					testNode.innerHTML = '<select><option selected="selected">a</option></select>';
					features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
				} catch(e){};
	
				// IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
				try {
					testNode.innerHTML = '<a class=""></a>';
					features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
				} catch(e){};
	
			}
	
			// IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
			try {
				testNode.innerHTML = '<form action="s"><input id="action"/></form>';
				brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
			} catch(e){};
	
			// native matchesSelector function
	
			features.nativeMatchesSelector = root.matchesSelector || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
			if (features.nativeMatchesSelector) try {
				// if matchesSelector trows errors on incorrect sintaxes we can use it
				features.nativeMatchesSelector.call(root, ':slick');
				features.nativeMatchesSelector = null;
			} catch(e){};
	
		}
	
		try {
			root.slick_expando = 1;
			delete root.slick_expando;
			features.getUID = this.getUIDHTML;
		} catch(e) {
			features.getUID = this.getUIDXML;
		}
	
		testRoot.removeChild(testNode);
		testNode = selected = testRoot = null;
	
		// getAttribute
	
		features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
			var method = this.attributeGetters[name];
			if (method) return method.call(node);
			var attributeNode = node.getAttributeNode(name);
			return (attributeNode) ? attributeNode.nodeValue : null;
		} : function(node, name){
			var method = this.attributeGetters[name];
			return (method) ? method.call(node) : node.getAttribute(name);
		};
	
		// hasAttribute
	
		features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
			return node.hasAttribute(attribute);
		} : function(node, attribute) {
			node = node.getAttributeNode(attribute);
			return !!(node && (node.specified || node.nodeValue));
		};
	
		// contains
		// FIXME: Add specs: local.contains should be different for xml and html documents?
		features.contains = (root && this.isNativeCode(root.contains)) ? function(context, node){
			return context.contains(node);
		} : (root && root.compareDocumentPosition) ? function(context, node){
			return context === node || !!(context.compareDocumentPosition(node) & 16);
		} : function(context, node){
			if (node) do {
				if (node === context) return true;
			} while ((node = node.parentNode));
			return false;
		};
	
		// document order sorting
		// credits to Sizzle (http://sizzlejs.com/)
	
		features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
			if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
			return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		} : ('sourceIndex' in root) ? function(a, b){
			if (!a.sourceIndex || !b.sourceIndex) return 0;
			return a.sourceIndex - b.sourceIndex;
		} : (document.createRange) ? function(a, b){
			if (!a.ownerDocument || !b.ownerDocument) return 0;
			var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
			aRange.setStart(a, 0);
			aRange.setEnd(a, 0);
			bRange.setStart(b, 0);
			bRange.setEnd(b, 0);
			return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		} : null ;
	
		root = null;
	
		for (feature in features){
			this[feature] = features[feature];
		}
	};
	
	// Main Method
	
	var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
		reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
		qsaFailExpCache = {};
	
	local.search = function(context, expression, append, first){
	
		var found = this.found = (first) ? null : (append || []);
		
		if (!context) return found;
		else if (context.navigator) context = context.document; // Convert the node from a window to a document
		else if (!context.nodeType) return found;
	
		// setup
	
		var parsed, i,
			uniques = this.uniques = {},
			hasOthers = !!(append && append.length),
			contextIsDocument = (context.nodeType == 9);
	
		if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
	
		// avoid duplicating items already in the append array
		if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
	
		// expression checks
	
		if (typeof expression == 'string'){ // expression is a string
	
			/*<simple-selectors-override>*/
			var simpleSelector = expression.match(reSimpleSelector);
			simpleSelectors: if (simpleSelector) {
	
				var symbol = simpleSelector[1],
					name = simpleSelector[2],
					node, nodes;
	
				if (!symbol){
	
					if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
					nodes = context.getElementsByTagName(name);
					if (first) return nodes[0] || null;
					for (i = 0; node = nodes[i++];){
						if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
					}
	
				} else if (symbol == '#'){
	
					if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
					node = context.getElementById(name);
					if (!node) return found;
					if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
					if (first) return node || null;
					if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
	
				} else if (symbol == '.'){
	
					if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
					if (context.getElementsByClassName && !this.brokenGEBCN){
						nodes = context.getElementsByClassName(name);
						if (first) return nodes[0] || null;
						for (i = 0; node = nodes[i++];){
							if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
						}
					} else {
						var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
						nodes = context.getElementsByTagName('*');
						for (i = 0; node = nodes[i++];){
							className = node.className;
							if (!(className && matchClass.test(className))) continue;
							if (first) return node;
							if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
						}
					}
	
				}
	
				if (hasOthers) this.sort(found);
				return (first) ? null : found;
	
			}
			/*</simple-selectors-override>*/
	
			/*<query-selector-override>*/
			querySelector: if (context.querySelectorAll) {
	
				if (!this.isHTMLDocument
					|| qsaFailExpCache[expression]
					//TODO: only skip when expression is actually mixed case
					|| this.brokenMixedCaseQSA
					|| (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
					|| (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
					|| (!contextIsDocument //Abort when !contextIsDocument and...
						//  there are multiple expressions in the selector
						//  since we currently only fix non-document rooted QSA for single expression selectors
						&& expression.indexOf(',') > -1
					)
					|| Slick.disableQSA
				) break querySelector;
	
				var _expression = expression, _context = context;
				if (!contextIsDocument){
					// non-document rooted QSA
					// credits to Andrew Dupont
					var currentId = _context.getAttribute('id'), slickid = 'slickid__';
					_context.setAttribute('id', slickid);
					_expression = '#' + slickid + ' ' + _expression;
					context = _context.parentNode;
				}
	
				try {
					if (first) return context.querySelector(_expression) || null;
					else nodes = context.querySelectorAll(_expression);
				} catch(e) {
					qsaFailExpCache[expression] = 1;
					break querySelector;
				} finally {
					if (!contextIsDocument){
						if (currentId) _context.setAttribute('id', currentId);
						else _context.removeAttribute('id');
						context = _context;
					}
				}
	
				if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
					if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
				} else for (i = 0; node = nodes[i++];){
					if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
				}
	
				if (hasOthers) this.sort(found);
				return found;
	
			}
			/*</query-selector-override>*/
	
			parsed = this.Slick.parse(expression);
			if (!parsed.length) return found;
		} else if (expression == null){ // there is no expression
			return found;
		} else if (expression.Slick){ // expression is a parsed Slick object
			parsed = expression;
		} else if (this.contains(context.documentElement || context, expression)){ // expression is a node
			(found) ? found.push(expression) : found = expression;
			return found;
		} else { // other junk
			return found;
		}
	
		/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
	
		// cache elements for the nth selectors
	
		this.posNTH = {};
		this.posNTHLast = {};
		this.posNTHType = {};
		this.posNTHTypeLast = {};
	
		/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
	
		// if append is null and there is only a single selector with one expression use pushArray, else use pushUID
		this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
	
		if (found == null) found = [];
	
		// default engine
	
		var j, m, n;
		var combinator, tag, id, classList, classes, attributes, pseudos;
		var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
	
		search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
	
			combinator = 'combinator:' + currentBit.combinator;
			if (!this[combinator]) continue search;
	
			tag        = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
			id         = currentBit.id;
			classList  = currentBit.classList;
			classes    = currentBit.classes;
			attributes = currentBit.attributes;
			pseudos    = currentBit.pseudos;
			lastBit    = (j === (currentExpression.length - 1));
	
			this.bitUniques = {};
	
			if (lastBit){
				this.uniques = uniques;
				this.found = found;
			} else {
				this.uniques = {};
				this.found = [];
			}
	
			if (j === 0){
				this[combinator](context, tag, id, classes, attributes, pseudos, classList);
				if (first && lastBit && found.length) break search;
			} else {
				if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
					this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
					if (found.length) break search;
				} else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
			}
	
			currentItems = this.found;
		}
	
		// should sort if there are nodes in append and if you pass multiple expressions.
		if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
	
		return (first) ? (found[0] || null) : found;
	};
	
	// Utils
	
	local.uidx = 1;
	local.uidk = 'slick-uniqueid';
	
	local.getUIDXML = function(node){
		var uid = node.getAttribute(this.uidk);
		if (!uid){
			uid = this.uidx++;
			node.setAttribute(this.uidk, uid);
		}
		return uid;
	};
	
	local.getUIDHTML = function(node){
		return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
	};
	
	// sort based on the setDocument documentSorter method.
	
	local.sort = function(results){
		if (!this.documentSorter) return results;
		results.sort(this.documentSorter);
		return results;
	};
	
	/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
	
	local.cacheNTH = {};
	
	local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
	
	local.parseNTHArgument = function(argument){
		var parsed = argument.match(this.matchNTH);
		if (!parsed) return false;
		var special = parsed[2] || false;
		var a = parsed[1] || 1;
		if (a == '-') a = -1;
		var b = +parsed[3] || 0;
		parsed =
			(special == 'n')	? {a: a, b: b} :
			(special == 'odd')	? {a: 2, b: 1} :
			(special == 'even')	? {a: 2, b: 0} : {a: 0, b: a};
	
		return (this.cacheNTH[argument] = parsed);
	};
	
	local.createNTHPseudo = function(child, sibling, positions, ofType){
		return function(node, argument){
			var uid = this.getUID(node);
			if (!this[positions][uid]){
				var parent = node.parentNode;
				if (!parent) return false;
				var el = parent[child], count = 1;
				if (ofType){
					var nodeName = node.nodeName;
					do {
						if (el.nodeName != nodeName) continue;
						this[positions][this.getUID(el)] = count++;
					} while ((el = el[sibling]));
				} else {
					do {
						if (el.nodeType != 1) continue;
						this[positions][this.getUID(el)] = count++;
					} while ((el = el[sibling]));
				}
			}
			argument = argument || 'n';
			var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
			if (!parsed) return false;
			var a = parsed.a, b = parsed.b, pos = this[positions][uid];
			if (a == 0) return b == pos;
			if (a > 0){
				if (pos < b) return false;
			} else {
				if (b < pos) return false;
			}
			return ((pos - b) % a) == 0;
		};
	};
	
	/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
	
	local.pushArray = function(node, tag, id, classes, attributes, pseudos){
		if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
	};
	
	local.pushUID = function(node, tag, id, classes, attributes, pseudos){
		var uid = this.getUID(node);
		if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
			this.uniques[uid] = true;
			this.found.push(node);
		}
	};
	
	local.matchNode = function(node, selector){
		if (this.isHTMLDocument && this.nativeMatchesSelector){
			try {
				return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
			} catch(matchError) {}
		}
		
		var parsed = this.Slick.parse(selector);
		if (!parsed) return true;
	
		// simple (single) selectors
		var expressions = parsed.expressions, reversedExpressions, simpleExpCounter = 0, i;
		for (i = 0; (currentExpression = expressions[i]); i++){
			if (currentExpression.length == 1){
				var exp = currentExpression[0];
				if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
				simpleExpCounter++;
			}
		}
	
		if (simpleExpCounter == parsed.length) return false;
	
		var nodes = this.search(this.document, parsed), item;
		for (i = 0; item = nodes[i++];){
			if (item === node) return true;
		}
		return false;
	};
	
	local.matchPseudo = function(node, name, argument){
		var pseudoName = 'pseudo:' + name;
		if (this[pseudoName]) return this[pseudoName](node, argument);
		var attribute = this.getAttribute(node, name);
		return (argument) ? argument == attribute : !!attribute;
	};
	
	local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
		if (tag){
			var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
			if (tag == '*'){
				if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
			} else {
				if (nodeName != tag) return false;
			}
		}
	
		if (id && node.getAttribute('id') != id) return false;
	
		var i, part, cls;
		if (classes) for (i = classes.length; i--;){
			cls = node.getAttribute('class') || node.className;
			if (!(cls && classes[i].regexp.test(cls))) return false;
		}
		if (attributes) for (i = attributes.length; i--;){
			part = attributes[i];
			if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
		}
		if (pseudos) for (i = pseudos.length; i--;){
			part = pseudos[i];
			if (!this.matchPseudo(node, part.key, part.value)) return false;
		}
		return true;
	};
	
	var combinators = {
	
		' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
	
			var i, item, children;
	
			if (this.isHTMLDocument){
				getById: if (id){
					item = this.document.getElementById(id);
					if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
						// all[id] returns all the elements with that name or id inside node
						// if theres just one it will return the element, else it will be a collection
						children = node.all[id];
						if (!children) return;
						if (!children[0]) children = [children];
						for (i = 0; item = children[i++];){
							var idNode = item.getAttributeNode('id');
							if (idNode && idNode.nodeValue == id){
								this.push(item, tag, null, classes, attributes, pseudos);
								break;
							}
						} 
						return;
					}
					if (!item){
						// if the context is in the dom we return, else we will try GEBTN, breaking the getById label
						if (this.contains(this.root, node)) return;
						else break getById;
					} else if (this.document !== node && !this.contains(node, item)) return;
					this.push(item, tag, null, classes, attributes, pseudos);
					return;
				}
				getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
					children = node.getElementsByClassName(classList.join(' '));
					if (!(children && children.length)) break getByClass;
					for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
					return;
				}
			}
			getByTag: {
				children = node.getElementsByTagName(tag);
				if (!(children && children.length)) break getByTag;
				if (!this.brokenStarGEBTN) tag = null;
				for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
			}
		},
	
		'>': function(node, tag, id, classes, attributes, pseudos){ // direct children
			if ((node = node.firstChild)) do {
				if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
			} while ((node = node.nextSibling));
		},
	
		'+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
			while ((node = node.nextSibling)) if (node.nodeType == 1){
				this.push(node, tag, id, classes, attributes, pseudos);
				break;
			}
		},
	
		'^': function(node, tag, id, classes, attributes, pseudos){ // first child
			node = node.firstChild;
			if (node){
				if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
				else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
			}
		},
	
		'~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
			while ((node = node.nextSibling)){
				if (node.nodeType != 1) continue;
				var uid = this.getUID(node);
				if (this.bitUniques[uid]) break;
				this.bitUniques[uid] = true;
				this.push(node, tag, id, classes, attributes, pseudos);
			}
		},
	
		'++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
			this['combinator:+'](node, tag, id, classes, attributes, pseudos);
			this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
		},
	
		'~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
			this['combinator:~'](node, tag, id, classes, attributes, pseudos);
			this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
		},
	
		'!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
			while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
		},
	
		'!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
			node = node.parentNode;
			if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
		},
	
		'!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
			while ((node = node.previousSibling)) if (node.nodeType == 1){
				this.push(node, tag, id, classes, attributes, pseudos);
				break;
			}
		},
	
		'!^': function(node, tag, id, classes, attributes, pseudos){ // last child
			node = node.lastChild;
			if (node){
				if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
				else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
			}
		},
	
		'!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
			while ((node = node.previousSibling)){
				if (node.nodeType != 1) continue;
				var uid = this.getUID(node);
				if (this.bitUniques[uid]) break;
				this.bitUniques[uid] = true;
				this.push(node, tag, id, classes, attributes, pseudos);
			}
		}
	
	};
	
	for (var c in combinators) local['combinator:' + c] = combinators[c];
	
	var pseudos = {
	
		/*<pseudo-selectors>*/
	
		'empty': function(node){
			var child = node.firstChild;
			return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
		},
	
		'not': function(node, expression){
			return !this.matchNode(node, expression);
		},
	
		'contains': function(node, text){
			return (node.innerText || node.textContent || '').indexOf(text) > -1;
		},
	
		'first-child': function(node){
			while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
			return true;
		},
	
		'last-child': function(node){
			while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
			return true;
		},
	
		'only-child': function(node){
			var prev = node;
			while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
			var next = node;
			while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
			return true;
		},
	
		/*<nth-pseudo-selectors>*/
	
		'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
	
		'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
	
		'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
	
		'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
	
		'index': function(node, index){
			return this['pseudo:nth-child'](node, '' + index + 1);
		},
	
		'even': function(node){
			return this['pseudo:nth-child'](node, '2n');
		},
	
		'odd': function(node){
			return this['pseudo:nth-child'](node, '2n+1');
		},
	
		/*</nth-pseudo-selectors>*/
	
		/*<of-type-pseudo-selectors>*/
	
		'first-of-type': function(node){
			var nodeName = node.nodeName;
			while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
			return true;
		},
	
		'last-of-type': function(node){
			var nodeName = node.nodeName;
			while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
			return true;
		},
	
		'only-of-type': function(node){
			var prev = node, nodeName = node.nodeName;
			while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
			var next = node;
			while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
			return true;
		},
	
		/*</of-type-pseudo-selectors>*/
	
		// custom pseudos
	
		'enabled': function(node){
			return !node.disabled;
		},
	
		'disabled': function(node){
			return node.disabled;
		},
	
		'checked': function(node){
			return node.checked || node.selected;
		},
	
		'focus': function(node){
			return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
		},
	
		'root': function(node){
			return (node === this.root);
		},
		
		'selected': function(node){
			return node.selected;
		}
	
		/*</pseudo-selectors>*/
	};
	
	for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
	
	// attributes methods
	
	local.attributeGetters = {
	
		'class': function(){
			return this.getAttribute('class') || this.className;
		},
	
		'for': function(){
			return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
		},
	
		'href': function(){
			return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
		},
	
		'style': function(){
			return (this.style) ? this.style.cssText : this.getAttribute('style');
		},
		
		'tabindex': function(){
			var attributeNode = this.getAttributeNode('tabindex');
			return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
		},
	
		'type': function(){
			return this.getAttribute('type');
		}
	
	};
	
	// Slick
	
	var Slick = local.Slick = (this.Slick || {});
	
	Slick.version = '1.1.5';
	
	// Slick finder
	
	Slick.search = function(context, expression, append){
		return local.search(context, expression, append);
	};
	
	Slick.find = function(context, expression){
		return local.search(context, expression, null, true);
	};
	
	// Slick containment checker
	
	Slick.contains = function(container, node){
		local.setDocument(container);
		return local.contains(container, node);
	};
	
	// Slick attribute getter
	
	Slick.getAttribute = function(node, name){
		return local.getAttribute(node, name);
	};
	
	// Slick matcher
	
	Slick.match = function(node, selector){
		if (!(node && selector)) return false;
		if (!selector || selector === node) return true;
		local.setDocument(node);
		return local.matchNode(node, selector);
	};
	
	// Slick attribute accessor
	
	Slick.defineAttributeGetter = function(name, fn){
		local.attributeGetters[name] = fn;
		return this;
	};
	
	Slick.lookupAttributeGetter = function(name){
		return local.attributeGetters[name];
	};
	
	// Slick pseudo accessor
	
	Slick.definePseudo = function(name, fn){
		local['pseudo:' + name] = function(node, argument){
			return fn.call(node, argument);
		};
		return this;
	};
	
	Slick.lookupPseudo = function(name){
		var pseudo = local['pseudo:' + name];
		if (pseudo) return function(argument){
			return pseudo.call(this, argument);
		};
		return null;
	};
	
	// Slick overrides accessor
	
	Slick.override = function(regexp, fn){
		local.override(regexp, fn);
		return this;
	};
	
	Slick.isXML = local.isXML;
	
	Slick.uidOf = function(node){
		return local.getUIDHTML(node);
	};
	
	if (!this.Slick) this.Slick = Slick;
	
	}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);

});